/*
 * Copyright (C) 2023  即时通讯网(52im.net) & Jack Jiang.
 * The MobileIMSDK-Uniapp客户端 Project. All rights reserved.
 *
 * 【本产品为著作权产品，请在授权范围内放心使用，禁止外传！】
 *
 * 【本系列产品在国家版权局的著作权登记信息如下】：
 * 1）国家版权局登记名（简称）和证书号：RainbowChat（软著登字第1220494号）
 * 2）国家版权局登记名（简称）和证书号：RainbowChat-Web（软著登字第3743440号）
 * 3）国家版权局登记名（简称）和证书号：RainbowAV（软著登字第2262004号）
 * 4）国家版权局登记名（简称）和证书号：MobileIMSDK-Web（软著登字第2262073号）
 * 5）国家版权局登记名（简称）和证书号：MobileIMSDK（软著登字第1220581号）
 * 著作权所有人：江顺/苏州网际时代信息科技有限公司
 *
 * 【违法或违规使用投诉和举报方式】：
 * 联系邮件：jack.jiang@52im.net
 * 联系微信：hellojackjiang
 * 联系QQ： 413980957
 * 官方社区：http://www.52im.net
 */
/**
 * 本地 WebSocket 实例封装实用类（基于Uniapp原生WebSocket，无任何第3方库依赖）。
 *
 * 本类提供存取本地WebSocket通信对象引用的方便方法，封装了WebSocket有效性判断以及异常处理等，以便确
 * 保调用者通过方法 {@link #getLocalSocket()}拿到的Socket对象是健康有效的。
 *
 * 依据作者对MobileIMSDK API的设计理念，本类将以单例的形式提供给调用者使用。
 *
 * @author Jack Jiang(http://www.52im.net/thread-2792-1-1.html)
 */
class ModuleMBSocketProvider {

	// 构造器（相当于java里的构造方法）
	constructor(argument) {
		this.TAG = "MBSocketProvider";

		/* 本地WebSocket对象引用 */
		this.localSocket = null;
		this.reset_localSocket = false;

		/*
		 * 连接完成后将被通知的观察者。如果设置本参数，则将在连接完成后调用1次，调用完成后置null。
		 * <p>
		 * 设置本观察者的目的，是因为WebSocket连接的过程是异常完成，有时希望在连接完成时就能立即执行想
		 * 要的逻辑，那么设置本观察者即可（在某次连接最终完成前，本参数的设置都会覆盖之前的设置，因为
		 * 只有这一个观察者可以用哦）。
		 */
		this.connectionDoneCallback = null;
	}

	/**
	 * 设置连接完成后将被通知的回调函数。如果设置本参数，则将在连接完成后调用1次，调用完成后置null。
	 * <p>
	 * 设置本回调函数的目的，是因为WebSocket连接的过程是异常完成，有时希望在连接完成时就能立即执行想
	 * 要的逻辑，那么设置本观察者即可（在某次连接最终完成前，本参数的设置都会覆盖之前的设置，因为
	 * 只有这一个观察者可以用哦）。
	 *
	 * @param connectionDoneCallback 回调函数
	 */
	setConnectionDoneCallback(connectionDoneCallback) {
		this.connectionDoneCallback = connectionDoneCallback;
	}

	/**
	 * 重置并新建一个全新的WebSocket对象。
	 *
	 * @return {SocketTask} 新建的全新Socket对象引用
	 */
	resetLocalSocket() {
		try {
			this.reset_localSocket = true;
			// 先无条件关闭socket（如果它还存在的话）
			this.closeLocalSocket(true);
			console.log(this.localSocket)
			if (this.localSocket) {
				this.localSocket.close();
			}
			console.log(this.localSocket)
			// 尝试连接服务器
			this.tryConnectToHost();
			console.log(this.localSocket)
			// 返回新建立的连接对象引用
			// setTimeout(() => {
				console.log(this.localSocket)
				return this.localSocket;
			// }, 1000)
		} catch (e) {
			console.log(e)
			uni.MBUtils.mblog_w(this.TAG, "重置localSocket时出错，原因是：" + e);
			// 无条件关闭socket（如果它还存在的话）
			this.closeLocalSocket(true);
			return null;
		}
	}

	/**
	 * 尝试发起连接并获取WebSocket。
	 *
	 * @return boolean
	 */
	tryConnectToHost() {
		if (uni.MBCore.debugEnabled())
			uni.MBUtils.mblog_d(this.TAG, "tryConnectToHost并获取connection开始了...");

		var done = false;
		// var that = this;

		//* Uniapp的WebSocket API文档请见：
		//* https://uniapp.dcloud.net.cn/api/request/socket-task.html

		try {
			//## 创建Uniapp的WebSocket实例👇
			//   详见：https://uniapp.dcloud.net.cn/api/request/websocket.html#connectsocket
			this.localSocket = uni.connectSocket({
				url: uni.MBCore.getWebsocketUrl(),
				header: {
					'content-type': 'text/xml'
				}, //'application/json'},
				// 连接成功异步回调：此回调只表示连接从应用层成功发起，但是不是真正的连接完成（因为断网情况下，这个也会回调哦，不要被误导）
				success: (res) => {

					uni.MBUtils.mblog_d(this.TAG, "WS.connectSocket - success (res=" + JSON.stringify(
						res) + ")！");
				},
				// 连接失败异步回调
				fail: (err) => {
					console.log(err)
					uni.MBUtils.mblog_w(this.TAG, "WS.connectSocket - fail (err=" + err + ")！");
				}
			})

			//## WebSocket连接建立时的回调处理👇
			//   注意：在标准HTML5的WebSocket API中，开发者直接设置onopen回调函数即可，而此处的回调是通onOpen的参数传过去的，这是二者API最大的区别！
			this.localSocket.onOpen((res) => {
				this.isLocalSocketReady()
				console.log(res)
				// if(this.reset_localSocket){
				// 	console.log(this.localSocket)
				// 	this.reset_localSocket = false;
				// 	return this.localSocket
				// }
				if (uni.MBCore.debugEnabled()) {
					uni.MBUtils.mblog_d(this.TAG, "WS.onopen - 连接已成功建立！(isLocalSocketReady=" + this
						.isLocalSocketReady() + ")");
				}

				// 连接结果回调通知
				if (this.connectionDoneCallback) {
					this.connectionDoneCallback(true);
					// 调用完成马上置空，确保本观察者只被调用一次
					this.connectionDoneCallback = null;
				}
			})

			//## WebSocket连接关闭时的回调处理👇
			this.localSocket.onClose((res) => {
				if (uni.MBCore.debugEnabled()) {
					// console.log(uni.MBCore.getWebsocketUrl());
					if (!this.isLocalSocketReady()) {
						// console.log(uni.MBCore.getWebsocketUrl());
						// console.log(uni.getStorageSync('loginInfo'));
						if (uni.getStorageSync('loginInfo') && uni.getStorageSync('loginInfo') != null &&
							uni.getStorageSync('loginInfo') != '' && uni.getStorageSync('loginInfo') != 0 &&
							JSON.stringify(uni.getStorageSync('loginInfo')) != '{}') {
							// setTimeout(() => {
							// this.resetLocalSocket()‘
							uni.IMSDK.loginImpl(uni.getStorageSync('loginInfo'), uni.MBCore
								.getWebsocketUrl(), false);


							// }, 1000);
						} else {
							// this.closeLocalSocket(true);
							uni.IMSDK.disconnectSocket();
							uni.MBSocketProvider.closeLocalSocket();
						}
					}


					// uni.MBUtils.mblog_d(this.TAG, "WS.onclose - 连接已断开。。。。(isLocalSocketReady=" + this
					// 	.isLocalSocketReady() +
					// 	", MBClientCoreSDK.connectedToServer=" + uni.MBCore.isConnectedToServer() + ")",
					// 	res);
				}

				// 用于快速响应连接断开事件，第一时间反馈给上层，提升用户体验
				if (uni.MBCore.isConnectedToServer()) {
					// if (uni.MBCore.debugEnabled())(
					// uni.MBKeepAliveDaemon.notifyConnectionLost();


					// uni.MBUtils.mblog_d(this.TAG,
					// 		"WS.onclose - 连接已断开，立即提前进入框架的“通信通道”断开处理逻辑(而不是等心跳线程探测到，那就已经比较迟了)......");

					// 进入框架的“通信通道”断开处理逻辑（即断开回调通知）

					// )

				}
			})

			//## WebSocket发生错误时的回调处理👇
			this.localSocket.onError((res) => {
				uni.MBUtils.mblog_e(this.TAG, "WS.onerror - 异常被触发了，原因是：", (res ? JSON.stringify(res) :
					'unkown'));

				// if (this.localSocket)
				//   this.localSocket.close();
				this.closeLocalSocket(true);
			})

			//## WebSocket收到数据时的回调处理👇
			this.localSocket.onMessage((res) => {
				let protocalJsonStr = (res ? (res.data ? res.data : null) : null);

				if (uni.MBCore.debugEnabled()) {
					// uni.MBUtils.mblog_d(this.TAG, "WS.onmessage - 收到消息(原始内容)：" + protocalJsonStr);	
				}

				// 读取收到的数据 Protocal 对象
				let pFromServer = (protocalJsonStr ? JSON.parse(protocalJsonStr) : null);

				// 进入消息调度和处理逻辑
				uni.MBDataReciever.handleProtocal(pFromServer);
			})

			done = true
		} catch (e) {
			uni.MBUtils.mblog_w(this.TAG, "连接Server(" + uni.MBCore.getWebsocketUrl() + ")失败：", e)
		}

		return done
	}

	/**
	 * 本类中的WebSocket对象是否是健康的。
	 *
	 * @return {boolean} true表示是健康的，否则不是
	 */
	isLocalSocketReady() {
		// 有关WebSocket的readyState状态说明，请见：
		// https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/readyState
		return this.localSocket != null && this.localSocket.readyState === 1;
	}

	/**
	 * 获得本地WebSocket的实例引用.
	 * <p>
	 * 本方法内封装了WebSocket有效性判断以及异常处理等，以便确保调用者通过本方法拿到的WebSocket对象是健康有效的。
	 *
	 * @return {SocketTask} 如果该实例正常则返回它的引用，否则返回null
	 * @see #isLocalSocketReady()
	 * @see #resetLocalSocket()
	 */
	getLocalSocket() {
		if (this.isLocalSocketReady()) {
			// TODO: 注释掉log！
			if (uni.MBCore.debugEnabled())
				uni.MBUtils.mblog_d(this.TAG, "isLocalSocketReady()==true，直接返回本地socket引用哦。");
			return this.localSocket;
		} else {

			// TODO: 注释掉log！
			if (uni.MBCore.debugEnabled())
				uni.MBUtils.mblog_d(this.TAG, "isLocalSocketReady()==false，需要先resetLocalSocket()...");
			return this.resetLocalSocket();
		}
	}

	/**
	 * 强制关闭本地WebSocket。
	 * 一旦调用本方法后，再次调用{@link #getLocalSocket()}将会返回一个全新的WebSocket对象引用。
	 *
	 * 本方法通常在两个场景下被调用：
	 * 1) 真正需要关闭WebSocket时（如所在的浏览器退出时）；
	 * 2) 当调用者检测到网络发生变动后希望重置以便获得健康的WebSocket引用对象时。
	 * 
	 * @param {boolean} silentClose true表示close前会清掉为此socket实例设置的各种回调，确保close时不会触发这些回调（比如：在
	 * 掉线重连场景下，close掉前一个旧的失效socket就不会错误地触发这些回调，而且还因为close是异步的，从而会引发一系列不可预测的行为）。
	 */
	closeLocalSocket(silentClose) {
		let silentCloseTag = (silentClose ? "silentClose=" + silentClose : "silentClose=false");

		if (uni.MBCore.debugEnabled())
			uni.MBUtils.mblog_d(this.TAG, "正在closeLocalSocket(" + silentCloseTag + ")...");

		// let that = this;
		let thatSocket = this.localSocket;
		if (thatSocket) {
			try {
				// close前会清掉为此socket实例设置的各种回调，确保close时不会触发这些回调（比如：在掉线重连场景下，close
				// 掉前一个旧的失效socket就不会错误地触发这些回调，而且还因为close是异步的，从而会引发一系列不可预测的行为）


				// 真正开始关闭socket了
				thatSocket.close({
					success: () => {
						uni.MBUtils.mblog_d(this.TAG, "closeLocalSocket(" + silentCloseTag +
							")：success.");
					},
					fail: () => {
						uni.MBUtils.mblog_w(this.TAG, "closeLocalSocket(" + silentCloseTag + ")：fail!");
					},
					complete: () => {
						uni.MBUtils.mblog_w(this.TAG, "closeLocalSocket(" + silentCloseTag +
							")：complete。");
					}
				});

				// 确保本次清理的socket实例是本方法初始时的那个实例（排除因异步等原因this.localSocket被重新设置后又在此被错误地置空，那就坑爹了）
				if (Object.is(thatSocket, this.localSocket)) {
					if (silentClose) {
						thatSocket.onError(null);
						thatSocket.onClose(null);
						thatSocket.onOpen(null);
						thatSocket.onMessage(null);

						if (uni.MBCore.debugEnabled())
							uni.MBUtils.mblog_d(this.TAG, "closeLocalSocket(" + silentCloseTag +
								")时，清理socket上的回调函数完成.");
					}

					this.localSocket = null;
				}
			} catch (e) {
				uni.MBUtils.mblog_w(this.TAG, "在closeLocalSocket(" + silentCloseTag + ")方法中试图释放localSocket资源时：", e);
			}
		} else {
			uni.MBUtils.mblog_d(this.TAG, "Socket处于未初化状态（可能是您还未登陆），无需关闭。");
		}
	}
}

// 以全局单例的形式存在，方便使用
uni.MBSocketProvider = new ModuleMBSocketProvider(); //MBSocketProvider;